package com.xuzhiguang.lightnat.server.core.client;

import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONUtil;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.xuzhiguang.lightnat.server.core.exception.NatClientNotFoundException;
import com.xuzhiguang.lightnat.server.core.exception.NatProxyNotFoundException;
import org.springframework.beans.BeanUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class DefaultNatClientService implements NatClientService {

    private final static String FILE_NAME = "nat-client.json";

    private File file;

    private CopyOnWriteArrayList<DefaultNatClient> natClients;

    private final Gson gson = new Gson();

    public DefaultNatClientService() {
        init();
    }

    private void init() {
        this.file = new File(System.getProperty("user.home") +  File.separator + FILE_NAME);

        if (!this.file.exists()) {
            try {
                this.file.createNewFile();
            } catch (IOException e) {
                throw new RuntimeException("create config file fail", e);
            }
            FileUtil.writeBytes("[]".getBytes(StandardCharsets.UTF_8), this.file);
        }

        List<DefaultNatClient> list;
        try {
            list = gson.fromJson(new FileReader(file), new TypeToken<List<DefaultNatClient>>(){}.getType());
        } catch (FileNotFoundException e) {
            throw new RuntimeException("file not found", e);
        }
        natClients = new CopyOnWriteArrayList<>(list);
    }

    @Override
    public NatClient getNatClient(String token) {

        for (DefaultNatClient client : natClients) {
            if (client.getToken() != null && client.getToken().equals(token)) {
                return client;
            }
        }

        return null;
    }

    @Override
    public NatClient getNatClient(long clientId) {
        for (DefaultNatClient client : natClients) {
            if (client.getId() != null && client.getId().equals(clientId)) {
                return client;
            }
        }
        return null;
    }

    @Override
    public NatProxy getNatProxy(String proxyHost, Integer proxyPort) {
        for (DefaultNatClient client : natClients) {
            for (DefaultNatProxy proxy : client.getProxies()) {
                if (proxy.getProxyPort().equals(proxyPort) && ("*".equals(proxy.getProxyHost()) || proxyHost.equals(proxy.getProxyHost()))) {
                    return proxy;
                }
            }
        }
        return null;
    }

    @Override
    public NatProxy getNatProxy(long proxyId) {
        for (DefaultNatClient client : natClients) {
            for (DefaultNatProxy proxy : client.getProxies()) {
                if (proxy.getId() == proxyId) {
                    return proxy;
                }
            }
        }
        return null;
    }

    @Override
    public List<NatClient> getAllNatClients() {
        return new ArrayList<>(this.natClients);
    }

    @Override
    public List<NatProxy> getNatProxies(long clientId) throws NatClientNotFoundException {

        for (DefaultNatClient client : natClients) {
            if (client.getId() != null && client.getId().equals(clientId)) {
                return new LinkedList<>(client.getProxies());
            }
        }
        throw new NatClientNotFoundException("clientId '" + clientId + "' not found");
    }

    @Override
    public List<NatProxy> getAllProxies() {
        List<NatProxy> natProxies = new ArrayList<>();
        for (DefaultNatClient client : natClients) {
            natProxies.addAll(client.getProxies());
        }

        return natProxies;
    }

    @Override
    public void addNatClient(NatClient natClient) {
        DefaultNatClient defaultNatClient = new DefaultNatClient();
        BeanUtils.copyProperties(natClient, defaultNatClient);
        defaultNatClient.setProxies(new LinkedList<>());
        natClients.add(defaultNatClient);
        sync();
    }

    @Override
    public NatClient modifyNatClient(long clientId, NatClient natClient) throws NatClientNotFoundException {

        NatClient oldNatClient = getNatClient(clientId);
        if (oldNatClient == null) {
            throw new NatClientNotFoundException("clientId '" + clientId + "' not found");
        }

        DefaultNatClient returnNatClient = new DefaultNatClient();
        BeanUtils.copyProperties(oldNatClient, returnNatClient);

        DefaultNatClient defaultNatClient = (DefaultNatClient) oldNatClient;
        defaultNatClient.setName(natClient.getName());
        defaultNatClient.setExpireTime(natClient.getExpireTime());
        defaultNatClient.setToken(natClient.getToken());

        sync();

        return returnNatClient;
    }

    @Override
    public void addNatProxy(NatProxy natProxy) throws NatClientNotFoundException {
        DefaultNatProxy defaultNatProxy = new DefaultNatProxy();
        BeanUtils.copyProperties(natProxy, defaultNatProxy);
        NatClient natClient = this.getNatClient(natProxy.getClientId());
        if (natClient == null) {
            throw new NatClientNotFoundException("clientId '" + natProxy.getClientId() + "' not found");
        }
        DefaultNatClient defaultNatClient = (DefaultNatClient) natClient;
        defaultNatClient.getProxies().add(defaultNatProxy);

        sync();
    }

    @Override
    public NatProxy modifyNatProxy(long proxyId, NatProxy natProxy) throws NatProxyNotFoundException {
        NatProxy oldNatProxy = getNatProxy(proxyId);
        if (oldNatProxy == null) {
            throw new NatProxyNotFoundException("proxyId '" + proxyId + "' not found");
        }

        DefaultNatProxy returnNatProxy = new DefaultNatProxy();
        BeanUtils.copyProperties(oldNatProxy, returnNatProxy);

        DefaultNatProxy defaultNatProxy = (DefaultNatProxy) oldNatProxy;
        defaultNatProxy.setProxyPort(natProxy.getProxyPort());
        defaultNatProxy.setProxyHost(natProxy.getProxyHost());
        defaultNatProxy.setProxyType(natProxy.getProxyType());
        defaultNatProxy.setSourceHost(natProxy.getSourceHost());
        defaultNatProxy.setSourcePort(natProxy.getSourcePort());

        sync();

        return returnNatProxy;
    }

    @Override
    public void deleteNatClient(long clientId) {

        natClients.removeIf(defaultNatClient -> defaultNatClient.getId() == clientId);
    }

    @Override
    public void deleteNatProxy(long proxyId) {

        for (DefaultNatClient defaultNatClient : natClients) {
            defaultNatClient.getProxies().removeIf(defaultNatProxy -> defaultNatProxy.getId() == proxyId);
        }
    }

    private synchronized void sync() {
        FileUtil.writeBytes(JSONUtil.toJsonStr(natClients).getBytes(StandardCharsets.UTF_8), this.file);
    }
}
